﻿#include "myhead.h"
#include "pthread.h"

struct tasklist *head;  
struct threadpool *threa;  

long int size;  //存放总文件大小
long int dsize;  //存放每次拷贝累加的大小
char progress_bar[20]; //存放进度条
int flast = 0;   //判断是否是指定文件类型拷贝的标志位
char fun[10];  //存放指定类型的文件后缀

struct mypaths   //存放路径
{
	char sdir[256];   //存放源目录路径
	char ddir[256];	   //目标目录路径
		
};
int addsize(char *srcdir) //求总文件大小
{
	int ret;
	long int size1;
	struct stat statbuf;
	struct dirent *p;//定义一个结构体指针指向文件或目录进行操作
	struct mypaths *pd = malloc(sizeof(struct mypaths));
	//打开源文件夹递归读取里面的内容
	DIR *mydir=opendir(srcdir);
	if(mydir==NULL) //判断是否打开成功
	{
		perror("打开源路径失败\n");		
		return -1;
	}

	while((p=readdir(mydir))!=NULL)//循环遍历目录
	{
		
		//排除掉  .和.. 
		struct mypaths *name=malloc(sizeof(struct mypaths));
		if(strncmp(p->d_name,".",1)==0||strncmp(p->d_name,"..",1)==0)
			continue;   //此次循环不执行
		if(p->d_type==DT_DIR)	//如果是目录
		{
			bzero(pd->sdir,256);//清空原路径缓冲区
			sprintf(pd->sdir,"%s/%s",srcdir,p->d_name);//拼接源目录路径
			addsize(pd->sdir);//递归	
			
		}	
		else  //如果不是目录
		{
			sprintf(name->sdir,"%s/%s",srcdir,p->d_name);
			stat(name->sdir,&statbuf);
			size1=statbuf.st_size;
			size=size+size1;  
		}
		
	}
	
	free(pd);
}
 void *newcay(void *arg) //拷贝文件任务函数
{
	
	int i;
 	char buf[1024*1024]={};
	bzero(buf,1024*1024);
	int res = 0;
	struct mypaths *p=(struct mypaths *)arg;
	
 	int fd = open(p->sdir,O_RDWR);
	
	if(fd==-1){
		perror("open");
		return NULL;
	}
	int fd1 = open(p->ddir,O_RDWR|O_CREAT|O_APPEND,0777);
	if(fd==-1){
		perror("open1");
		return NULL;
	}
	while((res = read(fd,buf,1024*1024))>0)
	{
		//printf("res = %d\n",res);
		dsize = dsize + res;
		//printf("dsize = %lu\n",dsize);
		//printf("size = %lu\n",size);
			//全部文件拷贝的打印
			if(flast==0){
				printf("\r<<[%ld kb]/[%ld kb]>>   当前完成:%ld%%  [%-20s]\n",(dsize)/1024,size/1024,(dsize)*100/size,progress_bar);
				for(i=0;i<=((dsize+res)*100/(size*5));i++)
				{
					//printf("dsize = %lu\n",dsize);
					//printf("size = %lu\n",size);
					progress_bar[i] = '*';
				}
				fflush(stdout);
				
				if((dsize+res)*100/size==100)
				{
					printf("\r<<[%ld kb]/[%ld kb]>>   当前完成:%ld%%  [%-20s]\n",(dsize+res)/1024,size/1024,(dsize+res)*100/size,progress_bar);
					printf("\n");
					printf("               			 复制完成\n");		
					write(fd1,buf,res);
					break;
				}
			}	
			//指定文件拷贝的打印
			if(flast==1){ 
				printf("\r拷贝大小<<[%ld kb]>>\n",dsize/1024);
			}
		write(fd1,buf,res);
	}

	close(fd);
	close(fd1);   
	
}  
struct threadpool //用来表示线程池
{
	pthread_mutex_t mymutex; //互斥锁
	pthread_cond_t mycond;//条件变量
	pthread_t *id;//存放多个线程的ID号
	int threadnum;//记录当前线程池中的线程数目
	int tasknum; //统计当前任务链表中的任务数
	bool shutdown;//标记线程池处于开启还是处于关闭状态
	struct tasklist*task_list;
};

struct tasklist //任务节点  封装任务链表添加任务函数
{
	void*(*taskp)(void*arg); //指向任务的指针
	void*taskarg; //传递给任务的参数
	struct tasklist*next;
};

//从任务链表的头部的下一个节点取出任务然后处理
void *gettask(void *arg)
{
	struct threadpool *q=(struct threadpool *)arg;
	struct tasklist *p;
	while(1)
	{
		//上锁
		pthread_mutex_lock(&q->mymutex);
		while(q->tasknum<=0&&q->shutdown==false)
		{
			//printf("当前线程  %ld 阻塞在wait!\n",pthread_self());
			pthread_cond_wait(&q->mycond,&q->mymutex); //阻塞
		}
		if(q->tasknum<=0&&q->shutdown==true)
		{
			pthread_mutex_unlock(&q->mymutex);//添加解锁并退出，防止死锁出现
			break;
		}	
		//处理刚才取出来节点(定义一个指针p指向这个节点)
		p = q->task_list->next;
		//从头结点的下一个取出任务(删除头结点后面的一个节点)	
		q->task_list->next = p->next;
		q->tasknum--;
		(p->taskp)(p->taskarg);  //执行自定义函数
		//解锁
		pthread_mutex_unlock(&q->mymutex);
		free(p);
		
	}
	pthread_exit(NULL);//退出线程
}

//初始化线程池
struct threadpool *init_pool(int num)
{
	int i;
	struct threadpool *pool=malloc(sizeof(struct threadpool));
	pthread_mutex_init(&(pool->mymutex),NULL); //初始化互斥锁
	pool->id=malloc(num*sizeof(pthread_t));//初始化线程节点的成员id(线程池中多个线程的id)
	pool->threadnum=num;//初始化线程节点的成员threadnum(当前线程池中的线程数目)
	pool->tasknum=0; //最开始任务链表是空的  tasknum(当前任务链表中的任务数)
	pool->shutdown=false; //线程池没有关闭
	pool->task_list=head; //初始化头结点
	for(i=0; i<num; i++) //循环创建线程
		pthread_create(&(pool->id[i]),NULL,gettask,pool);
	return pool;
}

//初始化任务链表的表头
struct tasklist *init_list()
{
	struct tasklist *head=malloc(sizeof(struct tasklist));//申请头结点堆空间
	head->taskp=NULL;//初始化头节点成员taskp
	head->taskarg=NULL;//初始化头节点成员taskarg
	head->next=NULL;//头结点指向空
	return head;
	
}

//添加任务
int add_task(void*(*taskp)(void*arg),void*arg,struct threadpool *somepool)
{
	//准备新的节点
	struct tasklist *new_task = malloc(sizeof(struct tasklist)); //申请新节点的堆空间
	new_task->taskp=taskp;
	new_task->taskarg=arg;
	new_task->next=NULL;   //给新的任务节点初始化
	//上锁
	pthread_mutex_lock(&somepool->mymutex);
	//找到链表的尾部，尾插
	struct tasklist *tmp = somepool->task_list;
	while(tmp->next!=NULL)
		tmp= tmp->next;
	tmp->next = new_task; //添加新的任务节点	
	somepool->tasknum++;
	//解锁
	pthread_mutex_unlock(&somepool->mymutex);
	//唤醒线程
	pthread_cond_signal(&somepool->mycond);
}


//销毁线程池
int pool_destroy(struct threadpool *somepool)
{
	int i;
	//先关闭线程池
	somepool->shutdown=true; //核心代码
	//唤醒所有的线程
	pthread_cond_broadcast(&somepool->mycond); //核心代码
	for(i=0; i<somepool->threadnum; i++)
	{
		pthread_join(somepool->id[i],NULL);
	}
	free(somepool->task_list);
	free(somepool->id);
	free(somepool);
}

/////////////////////////////////////////////////////////////////////////////////////

//操作目录和文件    
int myreaddir(char *srcdir,char *destdir)
{
	int ret;
	struct dirent *p = NULL;//定义一个结构体指针指向文件或目录进行操作
	struct mypaths *pd = malloc(sizeof(struct mypaths));


	//打开源文件夹递归读取里面的内容
	DIR *mydir=opendir(srcdir);
	
	if(mydir==NULL) //判断是否打开成功
	{
		perror("打开源路径失败\n");
		printf("%s\n",srcdir);
		return -1;
	}
	
	//sprintf(pd->ddir,"%s/%s",srcdir,destdir); //拼接目标路径
	//新建目标路径目录文件夹
	ret=mkdir(destdir,0777);
	if(ret==-1)//判断是否新建成功
	{
		perror("新建目录文件夹");
		return -1;
	}
	
	while((p=readdir(mydir))!=NULL)//循环遍历目录
	{
		struct mypaths *q = malloc(sizeof(struct mypaths));
		//排除掉  .和.. 
		if(strncmp(p->d_name,".",1)==0)
			continue;   //此次循环不执行
		if(p->d_type==DT_DIR)	//如果是目录
		{
			bzero(pd->sdir,256);//清空原路径缓冲区
			sprintf(pd->sdir,"%s/%s",srcdir,p->d_name);//拼接源目录路径
			sprintf(pd->ddir,"%s/%s",destdir,p->d_name);
			myreaddir(pd->sdir,pd->ddir);//递归	
	
		}	
		else  //如果不是目录
		{
			//拼接得到文件的路径，注意不要重复使用同一个变量存放路径，很容易跟线程起冲突
			//add_task(拷贝单个文件,路径);

					if(strstr(p->d_name,fun)!=NULL&&flast==1){
						sprintf(q->sdir,"%s/%s",srcdir,p->d_name);//拼接源目录文件路径	
						sprintf(q->ddir,"%s/%s",destdir,p->d_name);//拼接目标文件路径							
						add_task(newcay,q,threa);
					}	
					if(flast==0){
						sprintf(q->sdir,"%s/%s",srcdir,p->d_name);//拼接源目录文件路径	
						sprintf(q->ddir,"%s/%s",destdir,p->d_name);//拼接目标文件路径							
						add_task(newcay,q,threa);
					}	
		}
		
	}	
	
	free(pd);
	
	
}

void print_dir(const char *path,int level) //树状打印目录和文件
{
    //打开目录
    DIR *dir = opendir(path);
	char buf[256] = {};
    if(!dir){
        perror("opendir");
        return;
    }

    //读取目录
    struct dirent *dp = NULL;
    while(dp = readdir(dir)){
        //.和..要忽略
        if(strcmp(dp->d_name,".")==0||strcmp(dp->d_name,"..")==0)
            continue;
		int k;
		for(k=0;k<level;k++)
		{
			printf("|");
			printf("      "); 
		}
        //目录
        if(dp->d_type==DT_DIR){
            printf("|----%s\n",dp->d_name);//打印目录
            sprintf(buf,"%s/%s",path,dp->d_name);
            print_dir(buf,level+1);
        }
        else{
			printf("|----:%s\n",dp->d_name);
        }
    }

    //关闭目录
    closedir(dir);
}


int main(void)
{
	
	int choice = 0;
	char path[256]={};   //存放原目录路径
	char path1[256]={};  //存放目标目录路径
	time_t start,end;   //定义获取时间

	while(1)
	{
		printf("		--------1、拷贝目录和文件    --------\n");
		printf("		--------2、树状打印目录和文件--------\n");
		printf("		--------3、选择文件类型拷贝  --------\n");
		printf("		--------4、      退出       ---------\n");
		
		while(scanf("%d",&choice)!=1){
			while(getchar()!='\n');
			printf("输入错误，请重新输入:");
		}	
			system("clear");
		while(getchar()!='\n');//清空输入数字的缓存
		
		switch(choice){
			case 1:
				//初始化任务链表的表头
				head =init_list();
				//初始化线程池
				threa=init_pool(50);		
	
				bzero(path,256);
				bzero(path1,256);
				printf("请输入你要拷贝的目录\n");
				scanf("%s",path);
				printf("请输入你要拷贝的目标路径\n");
				scanf("%s",path1);
				addsize(path);//求文件夹总大小
				
				start=time(NULL);  //开始计算时间
				flast = 0;
				myreaddir(path,path1);	 //操作目录和文件
				//sleep(1);
				end = time(NULL);   //结束计算时间
				//sleep(3);
				printf("总耗时:%ld 秒\n",(end-start));
				printf("请重新输入操作指令：\n");	
				
				//线程池的销毁(回收线程池中所有的线程)确保任务链表中的所有任务都处理完毕才回收
				pool_destroy(threa);	
				break;
			case 2:	
				bzero(path,256);
				printf("请输入你要打印的目录\n");
				scanf("%s",path);
				
				print_dir(path,0);   //树状打印目录和文件
				addsize(path);//求文件夹总大小
				printf("该目录总大小是%ld kb\n",size/1024);
				printf("请重新输入操作指令：\n");
				
				break;
			case 3:
				//初始化任务链表的表头
				head =init_list();
				//初始化线程池
				threa=init_pool(50);	
				
				bzero(path,256);
				bzero(path1,256);
				printf("请输入你要拷贝的目录\n");
				scanf("%s",path);
				printf("请输入你要拷贝的目标路径\n"); 
				scanf("%s",path1);	
				printf("请输入你要拷贝的文件类型后缀\n");
				scanf("%s",fun);
				addsize(path);//求文件夹总大小
				
				flast = 1;
				myreaddir(path,path1); //指定文件拷贝
				printf("\n");
				printf("%s文件拷贝完成\n",fun);
				printf("请重新输入操作指令：\n");
				
				//线程池的销毁(回收线程池中所有的线程)确保任务链表中的所有任务都处理完毕才回收
				pool_destroy(threa);					
				break;
			case 4:
				return 0;//退出
			default:
				printf("无效选择\n");
	
			}	
			
	}
}
